package com.sly.netty.netty;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.sly.netty.constant.ChatConstant;
import com.sly.netty.constant.RedisConstant;
import com.sly.netty.core.Message;
import com.sly.netty.core.UserLoginInfo;
import com.sly.netty.result.BaseResult;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import javax.annotation.Resource;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ConcurrentHashMap;

/**
 * netty 服务端处理器
 *
 * @author SLY
 * @date 2025/2/24
 */
@Slf4j
@Component
@ChannelHandler.Sharable
public class NettyServerHandler extends SimpleChannelInboundHandler<String> {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private Environment environment;
    @Resource
    private RestTemplate restTemplate;

    public static final ConcurrentHashMap<String, Channel> CHANNEL_MAP = new ConcurrentHashMap<>(128);
    public static final ConcurrentHashMap<String, String> USER_CHANNEL_ID_MAP = new ConcurrentHashMap<>(128);

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
        log.info(s);
        try {
            Message message = JSONUtil.toBean(s, Message.class);
            // 判断是否已保存，没保存就
            if (CHANNEL_MAP.get(message.getFromUser()) == null) {
                CHANNEL_MAP.put(message.getFromUser(), channelHandlerContext.channel());
                USER_CHANNEL_ID_MAP.put(channelHandlerContext.channel().id().asLongText(), message.getFromUser());
                UserLoginInfo userLoginInfo = new UserLoginInfo();
                userLoginInfo.setUserId(message.getFromUser());
                userLoginInfo.setServerAddress(getLocalAddress());
                stringRedisTemplate.opsForValue().set(getUserLoginKey(message.getFromUser()), JSONUtil.toJsonStr(userLoginInfo));
            }
            Channel channel = CHANNEL_MAP.get(message.getToUser());
            if (channel != null) {
                channel.writeAndFlush(message.getContent());
            } else {
                String userLoginStr = stringRedisTemplate.opsForValue().get(getUserLoginKey(message.getToUser()));
                if (StrUtil.isNotBlank(userLoginStr)) {
                    // 对方在线
                    UserLoginInfo userLoginInfo = JSONUtil.toBean(userLoginStr, UserLoginInfo.class);
                    redirect(message, userLoginInfo);
                } else {
                    log.info("目标用户没有在线，放入队列或者丢弃这个消息");
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }

    /**
     * 重定向到其它服务器
     *
     * @param message       消息对象
     * @param userLoginInfo 用户登录信息
     * @return {@link  BaseResult}
     */
    private BaseResult redirect(Message message, UserLoginInfo userLoginInfo) {
        String url = "http://" + userLoginInfo.getServerAddress() + ChatConstant.MSG_REDIRECT_URL;
        URI uri = UriComponentsBuilder.fromHttpUrl(url)
                .build()
                .encode(StandardCharsets.UTF_8)
                .toUri();
        // 请求参数
        RequestEntity<Message> requestEntity = RequestEntity
                .post(uri)
                .header("Accept", MediaType.APPLICATION_JSON.toString())
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .body(message);
        ResponseEntity<BaseResult> responseEntity = restTemplate.exchange(requestEntity, BaseResult.class);
        return responseEntity.getBody();
    }

    private String getLocalAddress() throws UnknownHostException {
        String localIp = InetAddress.getLocalHost().getHostAddress();
        String port = environment.getProperty("server.port");
        return localIp + ":" + port;
    }

    private String getUserLoginKey(String userId) {
        return RedisConstant.USER_LOGIN_PREFIX + userId;
    }


    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 表示 channel 处于就绪状态, 提示上线
        log.info("客户端上线{}", ctx.channel().remoteAddress());
        // 保存channelId
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        // 表示 channel 处于不活动状态, 提示离线了
        log.info("客户端下线{}", ctx.channel().remoteAddress());
        removeChannel(ctx);
        ctx.fireChannelInactive();
    }

    private static void removeChannel(ChannelHandlerContext ctx) {
        String userCode = USER_CHANNEL_ID_MAP.get(ctx.channel().id().asLongText());
        if (StrUtil.isNotBlank(userCode)) {
            CHANNEL_MAP.remove(userCode);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        //关闭通道
        removeChannel(ctx);
        ctx.close();
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        super.handlerRemoved(ctx);
    }
}
